/* ========================================================================
 * Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
 *
 * OPC Foundation MIT License 1.00
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * The complete license agreement can be found here:
 * http://opcfoundation.org/License/MIT/1.00/
 * ======================================================================*/

#nullable enable

using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace Opc.Ua.Bindings
{
    /// <summary>
    /// Represents an asynchronous message socket operation.
    /// </summary>
    public interface IMessageSocketAsyncEventArgs : IDisposable
    {
        /// <summary>
        /// Gets the data buffer to use with an asynchronous socket method.
        /// </summary>
        /// <returns>
        /// A System.Byte array that represents the data buffer to use with an asynchronous
        /// socket method.
        /// </returns>
        byte[]? Buffer { get; }

        /// <summary>
        /// Gets or sets an array of data buffers to use with an asynchronous socket method.
        /// </summary>
        /// <returns>
        /// An System.Collections.IList that represents an array of data buffers to use with
        /// an asynchronous socket method.
        /// </returns>
        BufferCollection? BufferList { get; set; }

        /// <summary>
        ///  Gets the number of bytes transferred in the socket operation.
        /// </summary>
        /// <returns>An System.Int32 that contains the number of bytes transferred in the socket operation.</returns>
        int BytesTransferred { get; }

        /// <summary>
        /// Gets the result of the asynchronous socket operation.
        /// </summary>
        /// <returns>true if error, false if ok</returns>
        bool IsSocketError { get; }

        /// <summary>
        /// Gets the SocketError string of the asynchronous socket operation.
        /// </summary>
        /// <returns>the socket error string </returns>
        string SocketErrorString { get; }

        /// <summary>
        ///  Gets or sets a user or application object associated with this asynchronous socket
        ///  operation.
        /// </summary>
        /// <returns>
        /// An object that represents the user or application object associated with this
        /// asynchronous socket operation.
        /// </returns>
        object? UserToken { get; set; }

        /// <summary>
        /// The event used to complete an asynchronous operation.
        /// </summary>
        /// <returns>the message socket</returns>
        event EventHandler<IMessageSocketAsyncEventArgs> Completed;

        /// <summary>
        /// Sets the data buffer to use with an asynchronous socket method.
        /// </summary>
        /// <param name="buffer">The data buffer to use with an asynchronous socket method.</param>
        /// <param name="offset">The offset, in bytes, in the data buffer where the operation starts.</param>
        /// <param name="count">The maximum amount of data, in bytes, to send or receive in the buffer.</param>
        ///
        void SetBuffer(byte[] buffer, int offset, int count);
    }

    /// <summary>
    /// An interface to an object that received messages from the socket.
    /// </summary>
    public interface IMessageSink
    {
        /// <summary>
        /// Indicates that the sender side of the channel
        /// has too many outstanding messages that were not yet delivered.
        /// </summary>
        bool ChannelFull { get; }

        /// <summary>
        /// Called when a new message arrives.
        /// </summary>
        void OnMessageReceived(IMessageSocket source, ArraySegment<byte> message);

        /// <summary>
        /// Called when an error occurs during a read.
        /// </summary>
        void OnReceiveError(IMessageSocket source, ServiceResult result);
    }

    /// <summary>
    /// This is an interface to a message socket which supports a factory
    /// </summary>
    public interface IMessageSocketFactory
    {
        /// <summary>
        /// Creates an unconnected socket.
        /// </summary>
        /// <returns>the message socket</returns>
        IMessageSocket Create(
            IMessageSink sink,
            BufferManager bufferManager,
            int receiveBufferSize);

        /// <summary>
        /// Gets the implementation description.
        /// </summary>
        /// <value>The implementation string.</value>
        string Implementation { get; }
    }

    /// <summary>
    /// Handles reading and writing of message chunks over a socket.
    /// </summary>
    public interface IMessageSocket : IDisposable
    {
        /// <summary>
        /// Gets the socket handle.
        /// </summary>
        /// <value>The socket handle.</value>
        int Handle { get; }

        /// <summary>
        /// Gets the local endpoint.
        /// </summary>
        /// <exception cref="SocketException">An error occurred when attempting to access the socket.
        /// See the Remarks section for more information.</exception>
        /// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
        /// <returns>The System.Net.EndPoint that the Socket is using for communications.</returns>
        EndPoint? LocalEndpoint { get; }

        /// <summary>
        /// Gets the remote endpoint.
        /// </summary>
        /// <exception cref="SocketException">An error occurred when attempting to access the socket.
        /// See the Remarks section for more information.</exception>
        /// <exception cref="ObjectDisposedException">The Socket has been closed.</exception>
        /// <returns>The System.Net.EndPoint that the Socket is using for communications.</returns>
        EndPoint? RemoteEndpoint { get; }

        /// <summary>
        /// Returns the features implemented by the message socket.
        /// </summary>
        TransportChannelFeatures MessageSocketFeatures { get; }

        /// <summary>
        /// Connects to an endpoint.
        /// </summary>
        Task ConnectAsync(Uri endpointUrl, CancellationToken ct = default);

        /// <summary>
        /// Forcefully closes the socket.
        /// </summary>
        void Close();

        /// <summary>
        /// Starts reading messages from the socket.
        /// </summary>
        void ReadNextMessage();

        /// <summary>
        /// Changes the sink used to report reads.
        /// </summary>
        void ChangeSink(IMessageSink sink);

        /// <summary>
        /// Sends a buffer.
        /// </summary>
        bool Send(IMessageSocketAsyncEventArgs args);

        /// <summary>
        /// Get the message socket event args.
        /// </summary>
        IMessageSocketAsyncEventArgs MessageSocketEventArgs();
    }

    /// <summary>
    /// A channel, based on an underlying message socket
    /// </summary>
    public interface IMessageSocketChannel
    {
        /// <summary>
        /// Returns the channel's underlying message socket
        /// if connected - otherwise null.
        /// </summary>
        IMessageSocket? Socket { get; }
    }

    /// <summary>
    /// Extensions for message socket
    /// </summary>
    public static class MessageSocketExtensions
    {
        /// <summary>
        /// Connects to an endpoint using older style callback pattern.
        /// </summary>
        public static bool BeginConnect(
            this IMessageSocket socket,
            Uri endpointUrl,
            EventHandler<IMessageSocketAsyncEventArgs> callback,
            object? state)
        {
            Task t = socket.ConnectAsync(endpointUrl);
            t.GetAwaiter().OnCompleted(
                () => OnCompleted(socket, callback, state, t));
            return !t.IsFaulted;

            static void OnCompleted(
                IMessageSocket socket,
                EventHandler<IMessageSocketAsyncEventArgs> callback,
                object? state,
                Task t)
            {
                callback?.Invoke(
                    socket,
                    new TcpMessageSocketConnectAsyncEventArgs(t, state));
            }
        }

        /// <summary>
        /// Handles async event callbacks only for the ConnectAsync method
        /// </summary>
        private sealed class TcpMessageSocketConnectAsyncEventArgs :
            IMessageSocketAsyncEventArgs
        {
            /// <summary>
            /// Create the async event args for a TCP message socket.
            /// </summary>
            public TcpMessageSocketConnectAsyncEventArgs(Task result, object? state)
            {
                m_socketError = GetSocketError(result);
                UserToken = state;
            }

            /// <inheritdoc/>
            public void Dispose()
            {
            }

            /// <inheritdoc/>
            public object? UserToken { get; set; }

            /// <inheritdoc/>
            /// <remarks>Not implemented here.</remarks>
            public void SetBuffer(byte[] buffer, int offset, int count)
            {
                throw new NotImplementedException();
            }

            /// <inheritdoc/>
            public bool IsSocketError => m_socketError != SocketError.Success;

            /// <inheritdoc/>
            public string SocketErrorString => m_socketError.ToString();

            /// <inheritdoc/>
            /// <remarks>Not implemented here.</remarks>
            public event EventHandler<IMessageSocketAsyncEventArgs> Completed
            {
                add => throw new NotImplementedException();
                remove => throw new NotImplementedException();
            }

            /// <inheritdoc/>
            public int BytesTransferred => 0;

            /// <inheritdoc/>
            /// <remarks>Not implemented here.</remarks>
            public byte[]? Buffer => null;

            /// <inheritdoc/>
            /// <remarks>Not implememnted here.</remarks>
            public BufferCollection? BufferList
            {
                get => null;
                set => throw new NotImplementedException();
            }

            private static SocketError GetSocketError(Task t)
            {
                if (t.IsFaulted)
                {
                    SocketException? se = t.Exception?
                        .Flatten().InnerExceptions
                        .OfType<SocketException>()
                        .FirstOrDefault();
                    return se?.SocketErrorCode ?? SocketError.SocketError;
                }
                return SocketError.Success;
            }

            private readonly SocketError m_socketError;
        }
    }
}
